﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile.Nodes
{
	abstract class NumericalConstant : Expression
	{
		private enum Type
		{
			Integer,
			Float
		}

		public override void ResolveCode(Class c, Dictionary<object, object> parents)
		{
			// nothing to resolve
		}

		public override void ResolveClassToThis(Class c)
		{
			// nothing to resolve
		}

		protected static ValueTable integerTable = new ValueTable();
		protected static ValueTable floatTable = new ValueTable();

		public static void SortTables()
		{
			integerTable.SortByFrequency();
			floatTable.SortByFrequency();
		}

		public static int[] GetIntegerTable()
		{
			List<int> output = new List<int>();
			foreach (string id in integerTable.GetIds())
			{
				output.Add(int.Parse(id));
			}
			return output.ToArray();
		}

		public static string[] GetFloatTable()
		{
			return floatTable.GetIds();
		}

		public abstract int Index { get; }
		public abstract bool IsInteger { get; }

		private class FloatConstant : NumericalConstant
		{
			private string value;

			public FloatConstant(string value)
			{
				this.value = value;
				floatTable.Register(value);
			}

			public FloatConstant(double value) : this(value.ToString()) { }

			public override int Index
			{
				get { return floatTable.GetId(this.value); }
			}

			public override bool IsInteger { get { return false; } }
		}

		private class IntegerConstant : NumericalConstant
		{
			private string value;

			public IntegerConstant(string value)
			{
				this.value = value;
				integerTable.Register(value);
			}

			public IntegerConstant(int value) : this(value.ToString()) { }

			public override int Index { get { return integerTable.GetId(this.value); } }

			public override bool IsInteger { get { return true; } }
		}

		public static NumericalConstant ParseNumber(Tokens tokens, bool base10Expected, bool recursed)
		{
			Token tokenInstance = tokens.PopInstance();
			string token = tokenInstance.Value;

			int radix = 10;
			if (token.Length > 2 && token.StartsWith("0x"))
			{
				radix = 16;
				token = token.Substring(2);
			}
			else if (token.Length > 1 && token.StartsWith("0"))
			{
				radix = 8;
				token = token.Substring(1);
			}

			if (radix != 10 && base10Expected)
			{
				throw ParserException.UnexpectedToken(tokens, tokenInstance);
			}

			int value = 0;
			int digit = 0;
			char c;
			for (int i = 0; i < token.Length; ++i)
			{
				c = token[i];
				if (radix == 10)
				{
					if (c < '0' || c > '9')
					{
						ThrowInvalidNumber(tokenInstance);
					}

					digit = c - '0';
				}
				else if (radix == 8)
				{
					if (c < '0' || c > '7')
					{
						ThrowInvalidNumber(tokenInstance);
					}
					digit = c - '0';
				}
				else // radix == 16
				{
					digit = -1;
					if (c >= '0' && c <= '9')
					{
						digit = c - '0';
					}
					else if (c >= 'a' && c <= 'f')
					{
						digit = c - 'a' + 10;
					}
					else if (c >= 'A' && c <= 'F')
					{
						digit = c - 'A' + 10;
					}

					if (digit == -1)
					{
						ThrowInvalidNumber(tokenInstance);
					}
				}

				value *= radix;
				value += digit;
			}

			if (radix == 10)
			{
				token = tokens.Peek();

				if (token == "f" || token == "F")
				{
					return new FloatConstant(value);
				}
				else if (token == "." && !recursed)
				{
					tokens.PopExpected(".");
					string base10digits = tokens.PopBase10Digits();
					string doubleValue = value.ToString() + "." + base10digits;

					if (tokens.PopIfPresent("e") || tokens.PopIfPresent("E"))
					{
						string exponent = tokens.PopBase10Digits();// 38274.23847e001 <-- is this valid with a leading 0's in the exponent?
						return new FloatConstant(double.Parse(doubleValue) * System.Math.Pow(10.0, int.Parse(exponent))); // size constraint on exponent?
					}

					return new FloatConstant(doubleValue);
				}
			}

			return new IntegerConstant(value);
		}

		private static void ThrowInvalidNumber(Token token)
		{
			throw new ParserException("Invalid numerical constant", token);
		}
	}
}
